Import the needed packages

library(tidyverse)
library(openxlsx)
library(stringr)
library(plyr)
library(varhandle)
library(reshape)
library(factoextra)

getwd()  # Check that working directory is correct. This code uses relative paths.
[1] "F:/Google Drive (tsakal@ucsb.edu)/Book Network Paper/analysis/PCA code"

Creating Community Tables

We write some functions that allow use to make a comparison table of all different communities and subcommunities. The end result is a table that has a list of the top books in a community and, next to it, the books in each of its subcommunities. We write two functions that help automate this.

The tables we create are not in tidy format – rather they are in a format for humans to read.

Functions

This first function extends a dataframe to be a given length by filling in all the extra spaces with NA. This is done so we can use the bind_cols function to place tables next to eachother.


extend.dataframe <- function(df, len){
  # Extends a dataframe to a certain length by filling in rows with NA
  # Todo: vectorize this function for speedup

  num.rows = nrow(df)
  
  for (i in 1:len){
    if (i > num.rows){
      df[i, ] = NA
    }
  }
  return(df)
}

The next function is the main code. It takes in a dataframe (rdf), the column we wish to order the results by (ordering), and whether we are breaking the entire network into communities or each community into subcommunities (subcomuns).


comparison.table <- function(rdf, ordering, subcomuns= FALSE){
  # rdf: A dataframe with each book, the community it is in, and other columns describing various metrics
  # ordering: The column name of the metric we wish to order by
  # subcomuns: Set to true if we are taking a dataframe corresponding to a community and breaking it into subcommunity. (This just changes the name of the column to match.)
  
  
  
  rdf = rdf[,c("title", "modularity_class", "eigencentrality", "Weighted.Degree", "Label")] # Reorder Columns and extract only the ones we care about.
  
  # Decide whether to call the modularity_class column communities or subcommunities
  community = "Community"
  if (subcomuns == TRUE){
    community = "Subcommunity"}
  
  # Rename the columns to better reflect paper's conventions
  rdf = rename(rdf,
    c(modularity_class = community,
    eigencentrality = "Eigencentrality",
    Weighted.Degree = "Weighted Degree",
    Label = "Goodreads Id",
    title = "Title")
    )
  
  
  # Make sure column we are ordering by is numeric instead of a factor
  rdf[["Eigencentrality"]] = as.numeric(as.character(rdf[["Eigencentrality"]]))
  rdf[["Weighted Degree"]] = as.numeric(as.character(rdf[["Weighted Degree"]]))
  
  # Round both eigencentrality and weighted degree columns.
  # Make sure to do this after sorting so order is maintained
  rdf[["Eigencentrality"]] = round(rdf[["Eigencentrality"]] , digits = 3)  
  rdf[["Weighted Degree"]] = round(rdf[["Weighted Degree"]] , digits = 2) 
  
  rdf = rdf[order(rdf[[ordering]], decreasing=TRUE),]
  
  # Get the number of books and communities
  c = count(rdf, community)
  num.communities = nrow(c)
  num.books = nrow(rdf)
  
  dflist = list()
  dflist = append(dflist, rdf)
  
  for (i in 0:(num.communities-1)){
    df = rdf[rdf[community] == i,]
    df = df[order(df[[ordering]], decreasing=TRUE),]
    df = extend.dataframe(df, num.books)  # Extend dataframe so that can use bind_cols
    df = df[,c("Title", "Eigencentrality", "Weighted Degree")]  # Drop the redundant community column
    
    dflist = append(dflist, df)
  }
  
  df.full = bind_cols(dflist)

             
  return(df.full)
  
}

Create the comparison tables

First let’s make a table the takes the reader and enjoyment networks and breaks each of them into a table of their subcommunities.

# For the reader and enjoyment network make two tables each, one sorted by eigencentrality and the other by weighted degree.
for (network.type in list("enjoyment", "reader")){
  for (ordering in list("Eigencentrality", "Weighted Degree")){
    
    print(str_glue("Creating comparison table for {network.type}, ordered by {ordering}."))
    
    # Create table
    df = read.delim2(str_glue("./raw data/full {network.type} network.csv"), sep = ",")  # Read the correct table here
    df = comparison.table(df, ordering)
    
    # Save table in the processed data folder
    path = str_glue("./processed data/comparison_table_{network.type}_by_{ordering}.xlsx")
    write.xlsx(df, path)
    
    print(df)
  }
}
Creating comparison table for enjoyment, ordered by Eigencentrality.
Creating comparison table for enjoyment, ordered by Weighted Degree.
Creating comparison table for reader, ordered by Eigencentrality.
Creating comparison table for reader, ordered by Weighted Degree.

Next we will do the same thing for each of the subcommunities. To make the raw data for these we manually exported each table from Gephi by taking the entire network, filtering out those books not in the focal modularity class, and recalculating the Gephi statistics we care about (modularity class, eigencentrality, and weighted degree).

# Create the same tables, but now for subcommunities of each community. 
for (network.type in list("enjoyment", "reader")){
  for (ordering in list("Eigencentrality", "Weighted Degree")){
    
    path = str_glue("./raw data/{network.type} subcommunities/")  # Folder with subcommunity data
    file_names = list.files(path, pattern="*.csv") # List all .csv files in this folder
    
    # Create the table for each file
    for (i in 1:length(file_names)){
      f = file_names[[i]]
      f = paste(path, f, sep = "")
      print(f)
      
      df = read.delim2(f, sep = ",")
      df.full = comparison.table(df, ordering, subcomuns = TRUE)
      
      write.xlsx(df.full, str_glue("./processed data/{network.type} subcommunities/comp_table_{network.type}_{i-1}_by_{ordering}.xlsx"))
  
    }
  }
}
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 0.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 1.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 2.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 3.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 4.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 5.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 6.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 0.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 1.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 2.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 3.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 4.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 5.csv"
[1] "./raw data/enjoyment subcommunities/Enjoyment Community 6.csv"
[1] "./raw data/reader subcommunities/Reader Community 0.csv"
[1] "./raw data/reader subcommunities/Reader Community 1.csv"
[1] "./raw data/reader subcommunities/Reader Community 2.csv"
[1] "./raw data/reader subcommunities/Reader Community 3.csv"
[1] "./raw data/reader subcommunities/Reader Community 4.csv"
[1] "./raw data/reader subcommunities/Reader Community 5.csv"
[1] "./raw data/reader subcommunities/Reader Community 0.csv"
[1] "./raw data/reader subcommunities/Reader Community 1.csv"
[1] "./raw data/reader subcommunities/Reader Community 2.csv"
[1] "./raw data/reader subcommunities/Reader Community 3.csv"
[1] "./raw data/reader subcommunities/Reader Community 4.csv"
[1] "./raw data/reader subcommunities/Reader Community 5.csv"

There’s a few last tables to create. The reader network breaks into five communities and the enjoyment network breaks into seven. It is more than possible that if we force the reader network to break into seven communities then they will be the same communities as the enjoyment network. Visa versa if we break the enjoyment network into only five communities.

We can change the community size by going back to the gephi files for our network and recalculating the modularity, but now tweaking the resolution until we get the correct number of communities.

For this check we don’t go through the steps of restricting the network to each subcommunity before calculating eigencentrality and weighted degree. This is because that step at most changes the ordering slightly and we don’t require it for a check to see if the communities are vaguely the same.

# Again, sort by eigencentrality and the other by weighted degree.

for (ordering in list("Eigencentrality", "Weighted Degree")){
  
  print(str_glue("Creating comparison table for {network.type}, ordered by {ordering}."))
  
  # Create table
  df = read.delim2(str_glue("./raw data/full enjoyment network 6 communities.csv"), sep = ",")  # Name of the new reader network data.
  df = comparison.table(df, ordering)
  
  # Save table in the processed data folder
  path = str_glue("./processed data/comparison_table_enjoyment_6_comns_by_{ordering}.xlsx")
  write.xlsx(df, path)
  
  print(df)

}
Creating comparison table for reader, ordered by Eigencentrality.
Creating comparison table for reader, ordered by Weighted Degree.

Can Subjects predict Community?

Now we ask whether a list of subjects for each book can predict the community it is in.

sdf = read.delim2("./raw data/subjects_reduced.csv", sep = ",", colClasses = "numeric", na.strings = "")  # read in dataframe of subjects
print(sdf)

# Rename the columns to better reflect paper's conventions
sdf = rename(sdf, c(X = "Goodreads Id")  )
print(sdf)
tally(sdf)


# For the reader and enjoyment network 
for (network.type in list("enjoyment", "reader")){
    
  print(str_glue("Creating subject table for {network.type}."))
  
  # Read book data table and rename columns
  df = read.delim2(str_glue("./raw data/full {network.type} network.csv"), sep = ",")  # Read the correct table here
  df = rename(df,
    c(modularity_class = "Community",
    eigencentrality = "Eigencentrality",
    Weighted.Degree = "Weighted Degree",
    Label = "Goodreads Id",
    title = "Title")
    )
  
  df = inner_join(sdf, df, by=c("Goodreads Id"))
  
  #
  
  # Save table in the processed data folder
  #path = str_glue("./processed data/comparison_table_{network.type}_by_{ordering}.xlsx")
  #write.xlsx(df, path)
  
  #df = lapply(df, function(x) as.numeric(as.character(x)))
  #df = do.call(cbind, df)
  
  # get the sums for all the subject columns. 
  non.sub.cols = c("Goodreads Id", "Title", "subjects", "Weighted Degree", "Eigencentrality", "clustering", "pageranks", "triangles", "Id", "Degree", "Community", "bipartite")
  df = colSums(df[,!names(df) %in% non.sub.cols], na.rm = TRUE )
  df[]
  df = df[order(df[["V1"]], decreasing=TRUE),]

  
  
  
  # Get the number of communities so can loop through them
  #c = count(df, "Community")
  #num.communities = nrow(c)
  #for (i in 0:(num.communities-1)){
  #  cdf = df[df["Community"] == i,]
  #  cdf = cdf[order(cdf[["Eigencentrality"]], decreasing=TRUE),]
  #  cdf = summarise_all(cdf)
  #  print(cdf)

  }
Creating subject table for enjoyment.
Error in df[["V1"]] : subscript out of bounds

PCA Analysis

We can’t use the pure subject counts beacuse without normalizing the data in some way we have that popular subjects are the most influencial and thus dominate the entire analysis.

Remeber than for the

library(readxl)

#df = read.delim2(str_glue("./raw data/relative_subject_counts_reader.csv"), sep = ",")  # Read the correct table here
df = read_excel("./raw data/relative_subject_counts_enjoyment.xlsx")  # Read the correct table here
New names:
* `` -> ...1
# Both the above tables give the same pca analysis

df = df %>% column_to_rownames(., var = "...1")  # Set label to row names. Var should equal the name of the first column.

num.communities = ncol(df) - 1  # Minus 1 all counts column

# Drop the all group because this makes all results the same
df = df[ , -which(names(df) %in% c("count_all"))]  

for (i in 0:(num.communities-1)){
  colname = str_glue("count_{i}")
  df[[colname]] = as.numeric(as.character(df[[colname]]))

}

# Rename the columns for interpretation
# Names for enjoyment
names(df)[1:num.communities] = c("Thriller", "Fantasy/Scifi", "Manga", "(Modern) Classics", "Children's", "Contemporary/Realistic", "Young Adult")
# Names for reader
#names(df)[1:num.communities] = c("Children's", "(Modern) Classics", "Genre Fiction", "Young Adult", "Contemporary/Realistic")


# Remove fiction row
#row.names.remove <- c("Fiction")
#df = df[!(row.names(df) %in% row.names.remove), ]

df["counts_all"] = NULL
subjects.pca = prcomp(df,center = TRUE, scale. = TRUE)
summary(subjects.pca)
Importance of components:
                          PC1    PC2    PC3    PC4    PC5    PC6     PC7
Standard deviation     1.3114 1.1183 1.0927 1.0221 0.9682 0.9207 0.07471
Proportion of Variance 0.2457 0.1787 0.1706 0.1492 0.1339 0.1211 0.00080
Cumulative Proportion  0.2457 0.4244 0.5949 0.7442 0.8781 0.9992 1.00000
fviz_eig(subjects.pca)

We see all components are important except the fifth. This makes sense because each of the eigenvectors should correspond to a community. If a pca could explain 100% of the variance in the number of communities that means that the communities had no overlap? So how far away from this we are corresponds to the mixing between genres.

coords = get_pca_ind(subjects.pca)$coord
coords.df = as.data.frame(coords)
get_pca(subjects.pca)$cor
                             Dim.1       Dim.2       Dim.3      Dim.4       Dim.5        Dim.6        Dim.7
Thriller                0.01628752  0.81721542  0.20812923  0.5325549 -0.06396264 -0.003437558 -0.029293099
Fantasy/Scifi           0.50922021 -0.04524416  0.52816728 -0.3382731  0.35775027  0.465452699 -0.025037911
Manga                   0.19226999 -0.01129886  0.46047943 -0.3212773 -0.78600667 -0.172609079 -0.006619265
(Modern) Classics      -0.84022900 -0.29614787  0.26417188 -0.1000065  0.18157684 -0.303043346 -0.041450941
Children's              0.14479598 -0.65192742 -0.09012555  0.6013011 -0.28274165  0.322349708 -0.022066254
Contemporary/Realistic -0.39605095  0.25442978 -0.58655752 -0.3912910 -0.26062305  0.461274899 -0.023490877
Young Adult             0.73450310 -0.05686448 -0.48763811 -0.1371158  0.08206854 -0.438918190 -0.035993250

fviz_pca_biplot(subjects.pca,
              col.var = "contrib", # Color by contributions to the PC
             #col.ind = "cos2", # Color by the quality of representation
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE,     # Avoid text overlapping
             #addEllipses = TRUE,
             label = c("quanti.sup", "var"),
             alpha.ind = .1,
             alpha.var = 1,
             
             )

NA
NA
fviz_pca_var(subjects.pca,
             col.var = "contrib", # Color by contributions to the PC
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE     # Avoid text overlapping
             )

library("corrplot")
corrplot 0.84 loaded
var <- get_pca_var(subjects.pca)
corrplot(var$contrib, is.corr=FALSE, method="square")


corrplot(var$cor, is.corr=FALSE, method="square", tl.col = 1)

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpJbXBvcnQgdGhlIG5lZWRlZCBwYWNrYWdlcw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShvcGVueGxzeCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkocGx5cikNCmxpYnJhcnkodmFyaGFuZGxlKQ0KbGlicmFyeShyZXNoYXBlKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KDQpnZXR3ZCgpICAjIENoZWNrIHRoYXQgd29ya2luZyBkaXJlY3RvcnkgaXMgY29ycmVjdC4gVGhpcyBjb2RlIHVzZXMgcmVsYXRpdmUgcGF0aHMuDQoNCmBgYA0KDQojIENyZWF0aW5nIENvbW11bml0eSBUYWJsZXMNCg0KV2Ugd3JpdGUgc29tZSBmdW5jdGlvbnMgdGhhdCBhbGxvdyB1c2UgdG8gbWFrZSBhIGNvbXBhcmlzb24gdGFibGUgb2YgYWxsIGRpZmZlcmVudCBjb21tdW5pdGllcyBhbmQgc3ViY29tbXVuaXRpZXMuIFRoZSBlbmQgcmVzdWx0IGlzIGEgdGFibGUgdGhhdCBoYXMgYSBsaXN0IG9mIHRoZSB0b3AgYm9va3MgaW4gYSBjb21tdW5pdHkgYW5kLCBuZXh0IHRvIGl0LCB0aGUgYm9va3MgaW4gZWFjaCBvZiBpdHMgc3ViY29tbXVuaXRpZXMuIFdlIHdyaXRlIHR3byBmdW5jdGlvbnMgdGhhdCBoZWxwIGF1dG9tYXRlIHRoaXMuDQoNClRoZSB0YWJsZXMgd2UgY3JlYXRlIGFyZSBub3QgaW4gdGlkeSBmb3JtYXQgLS0gcmF0aGVyIHRoZXkgYXJlIGluIGEgZm9ybWF0IGZvciBodW1hbnMgdG8gcmVhZC4gDQoNCiMjIEZ1bmN0aW9ucw0KDQpUaGlzIGZpcnN0IGZ1bmN0aW9uIGV4dGVuZHMgYSBkYXRhZnJhbWUgdG8gYmUgYSBnaXZlbiBsZW5ndGggYnkgZmlsbGluZyBpbiBhbGwgdGhlIGV4dHJhIHNwYWNlcyB3aXRoIE5BLiBUaGlzIGlzIGRvbmUgc28gd2UgY2FuIHVzZSB0aGUgYmluZF9jb2xzIGZ1bmN0aW9uIHRvIHBsYWNlIHRhYmxlcyBuZXh0IHRvIGVhY2hvdGhlci4NCg0KYGBge3J9DQoNCmV4dGVuZC5kYXRhZnJhbWUgPC0gZnVuY3Rpb24oZGYsIGxlbil7DQogICMgRXh0ZW5kcyBhIGRhdGFmcmFtZSB0byBhIGNlcnRhaW4gbGVuZ3RoIGJ5IGZpbGxpbmcgaW4gcm93cyB3aXRoIE5BDQogICMgVG9kbzogdmVjdG9yaXplIHRoaXMgZnVuY3Rpb24gZm9yIHNwZWVkdXANCg0KICBudW0ucm93cyA9IG5yb3coZGYpDQogIA0KICBmb3IgKGkgaW4gMTpsZW4pew0KICAgIGlmIChpID4gbnVtLnJvd3Mpew0KICAgICAgZGZbaSwgXSA9IE5BDQogICAgfQ0KICB9DQogIHJldHVybihkZikNCn0NCg0KYGBgDQoNClRoZSBuZXh0IGZ1bmN0aW9uIGlzIHRoZSBtYWluIGNvZGUuIEl0IHRha2VzIGluIGEgZGF0YWZyYW1lIChyZGYpLCB0aGUgY29sdW1uIHdlIHdpc2ggdG8gb3JkZXIgdGhlIHJlc3VsdHMgYnkgKG9yZGVyaW5nKSwgYW5kIHdoZXRoZXIgd2UgYXJlIGJyZWFraW5nIHRoZSBlbnRpcmUgbmV0d29yayBpbnRvIGNvbW11bml0aWVzIG9yIGVhY2ggY29tbXVuaXR5IGludG8gc3ViY29tbXVuaXRpZXMgKHN1YmNvbXVucykuDQoNCg0KYGBge3J9DQoNCmNvbXBhcmlzb24udGFibGUgPC0gZnVuY3Rpb24ocmRmLCBvcmRlcmluZywgc3ViY29tdW5zPSBGQUxTRSl7DQogICMgcmRmOiBBIGRhdGFmcmFtZSB3aXRoIGVhY2ggYm9vaywgdGhlIGNvbW11bml0eSBpdCBpcyBpbiwgYW5kIG90aGVyIGNvbHVtbnMgZGVzY3JpYmluZyB2YXJpb3VzIG1ldHJpY3MNCiAgIyBvcmRlcmluZzogVGhlIGNvbHVtbiBuYW1lIG9mIHRoZSBtZXRyaWMgd2Ugd2lzaCB0byBvcmRlciBieQ0KICAjIHN1YmNvbXVuczogU2V0IHRvIHRydWUgaWYgd2UgYXJlIHRha2luZyBhIGRhdGFmcmFtZSBjb3JyZXNwb25kaW5nIHRvIGEgY29tbXVuaXR5IGFuZCBicmVha2luZyBpdCBpbnRvIHN1YmNvbW11bml0eS4gKFRoaXMganVzdCBjaGFuZ2VzIHRoZSBuYW1lIG9mIHRoZSBjb2x1bW4gdG8gbWF0Y2guKQ0KICANCiAgDQogIA0KICByZGYgPSByZGZbLGMoInRpdGxlIiwgIm1vZHVsYXJpdHlfY2xhc3MiLCAiZWlnZW5jZW50cmFsaXR5IiwgIldlaWdodGVkLkRlZ3JlZSIsICJMYWJlbCIpXSAjIFJlb3JkZXIgQ29sdW1ucyBhbmQgZXh0cmFjdCBvbmx5IHRoZSBvbmVzIHdlIGNhcmUgYWJvdXQuDQogIA0KICAjIERlY2lkZSB3aGV0aGVyIHRvIGNhbGwgdGhlIG1vZHVsYXJpdHlfY2xhc3MgY29sdW1uIGNvbW11bml0aWVzIG9yIHN1YmNvbW11bml0aWVzDQogIGNvbW11bml0eSA9ICJDb21tdW5pdHkiDQogIGlmIChzdWJjb211bnMgPT0gVFJVRSl7DQogICAgY29tbXVuaXR5ID0gIlN1YmNvbW11bml0eSJ9DQogIA0KICAjIFJlbmFtZSB0aGUgY29sdW1ucyB0byBiZXR0ZXIgcmVmbGVjdCBwYXBlcidzIGNvbnZlbnRpb25zDQogIHJkZiA9IHJlbmFtZShyZGYsDQogICAgYyhtb2R1bGFyaXR5X2NsYXNzID0gY29tbXVuaXR5LA0KICAgIGVpZ2VuY2VudHJhbGl0eSA9ICJFaWdlbmNlbnRyYWxpdHkiLA0KICAgIFdlaWdodGVkLkRlZ3JlZSA9ICJXZWlnaHRlZCBEZWdyZWUiLA0KICAgIExhYmVsID0gIkdvb2RyZWFkcyBJZCIsDQogICAgdGl0bGUgPSAiVGl0bGUiKQ0KICAgICkNCiAgDQogIA0KICAjIE1ha2Ugc3VyZSBjb2x1bW4gd2UgYXJlIG9yZGVyaW5nIGJ5IGlzIG51bWVyaWMgaW5zdGVhZCBvZiBhIGZhY3Rvcg0KICByZGZbWyJFaWdlbmNlbnRyYWxpdHkiXV0gPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihyZGZbWyJFaWdlbmNlbnRyYWxpdHkiXV0pKQ0KICByZGZbWyJXZWlnaHRlZCBEZWdyZWUiXV0gPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihyZGZbWyJXZWlnaHRlZCBEZWdyZWUiXV0pKQ0KICANCiAgIyBSb3VuZCBib3RoIGVpZ2VuY2VudHJhbGl0eSBhbmQgd2VpZ2h0ZWQgZGVncmVlIGNvbHVtbnMuDQogICMgTWFrZSBzdXJlIHRvIGRvIHRoaXMgYWZ0ZXIgc29ydGluZyBzbyBvcmRlciBpcyBtYWludGFpbmVkDQogIHJkZltbIkVpZ2VuY2VudHJhbGl0eSJdXSA9IHJvdW5kKHJkZltbIkVpZ2VuY2VudHJhbGl0eSJdXSAsIGRpZ2l0cyA9IDMpICANCiAgcmRmW1siV2VpZ2h0ZWQgRGVncmVlIl1dID0gcm91bmQocmRmW1siV2VpZ2h0ZWQgRGVncmVlIl1dICwgZGlnaXRzID0gMikgDQogIA0KICByZGYgPSByZGZbb3JkZXIocmRmW1tvcmRlcmluZ11dLCBkZWNyZWFzaW5nPVRSVUUpLF0NCiAgDQogICMgR2V0IHRoZSBudW1iZXIgb2YgYm9va3MgYW5kIGNvbW11bml0aWVzDQogIGMgPSBjb3VudChyZGYsIGNvbW11bml0eSkNCiAgbnVtLmNvbW11bml0aWVzID0gbnJvdyhjKQ0KICBudW0uYm9va3MgPSBucm93KHJkZikNCiAgDQogIGRmbGlzdCA9IGxpc3QoKQ0KICBkZmxpc3QgPSBhcHBlbmQoZGZsaXN0LCByZGYpDQogIA0KICBmb3IgKGkgaW4gMDoobnVtLmNvbW11bml0aWVzLTEpKXsNCiAgICBkZiA9IHJkZltyZGZbY29tbXVuaXR5XSA9PSBpLF0NCiAgICBkZiA9IGRmW29yZGVyKGRmW1tvcmRlcmluZ11dLCBkZWNyZWFzaW5nPVRSVUUpLF0NCiAgICBkZiA9IGV4dGVuZC5kYXRhZnJhbWUoZGYsIG51bS5ib29rcykgICMgRXh0ZW5kIGRhdGFmcmFtZSBzbyB0aGF0IGNhbiB1c2UgYmluZF9jb2xzDQogICAgZGYgPSBkZlssYygiVGl0bGUiLCAiRWlnZW5jZW50cmFsaXR5IiwgIldlaWdodGVkIERlZ3JlZSIpXSAgIyBEcm9wIHRoZSByZWR1bmRhbnQgY29tbXVuaXR5IGNvbHVtbg0KICAgIA0KICAgIGRmbGlzdCA9IGFwcGVuZChkZmxpc3QsIGRmKQ0KICB9DQogIA0KICBkZi5mdWxsID0gYmluZF9jb2xzKGRmbGlzdCkNCg0KICAgICAgICAgICAgIA0KICByZXR1cm4oZGYuZnVsbCkNCiAgDQp9DQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBjb21wYXJpc29uIHRhYmxlcw0KDQpGaXJzdCBsZXQncyBtYWtlIGEgdGFibGUgdGhlIHRha2VzIHRoZSByZWFkZXIgYW5kIGVuam95bWVudCBuZXR3b3JrcyBhbmQgYnJlYWtzIGVhY2ggb2YgdGhlbSBpbnRvIGEgdGFibGUgb2YgdGhlaXIgc3ViY29tbXVuaXRpZXMuDQoNCmBgYHtyfQ0KIyBGb3IgdGhlIHJlYWRlciBhbmQgZW5qb3ltZW50IG5ldHdvcmsgbWFrZSB0d28gdGFibGVzIGVhY2gsIG9uZSBzb3J0ZWQgYnkgZWlnZW5jZW50cmFsaXR5IGFuZCB0aGUgb3RoZXIgYnkgd2VpZ2h0ZWQgZGVncmVlLg0KZm9yIChuZXR3b3JrLnR5cGUgaW4gbGlzdCgiZW5qb3ltZW50IiwgInJlYWRlciIpKXsNCiAgZm9yIChvcmRlcmluZyBpbiBsaXN0KCJFaWdlbmNlbnRyYWxpdHkiLCAiV2VpZ2h0ZWQgRGVncmVlIikpew0KICAgIA0KICAgIHByaW50KHN0cl9nbHVlKCJDcmVhdGluZyBjb21wYXJpc29uIHRhYmxlIGZvciB7bmV0d29yay50eXBlfSwgb3JkZXJlZCBieSB7b3JkZXJpbmd9LiIpKQ0KICAgIA0KICAgICMgQ3JlYXRlIHRhYmxlDQogICAgZGYgPSByZWFkLmRlbGltMihzdHJfZ2x1ZSgiLi9yYXcgZGF0YS9mdWxsIHtuZXR3b3JrLnR5cGV9IG5ldHdvcmsuY3N2IiksIHNlcCA9ICIsIikgICMgUmVhZCB0aGUgY29ycmVjdCB0YWJsZSBoZXJlDQogICAgZGYgPSBjb21wYXJpc29uLnRhYmxlKGRmLCBvcmRlcmluZykNCiAgICANCiAgICAjIFNhdmUgdGFibGUgaW4gdGhlIHByb2Nlc3NlZCBkYXRhIGZvbGRlcg0KICAgIHBhdGggPSBzdHJfZ2x1ZSgiLi9wcm9jZXNzZWQgZGF0YS9jb21wYXJpc29uX3RhYmxlX3tuZXR3b3JrLnR5cGV9X2J5X3tvcmRlcmluZ30ueGxzeCIpDQogICAgd3JpdGUueGxzeChkZiwgcGF0aCkNCiAgICANCiAgICBwcmludChkZikNCiAgfQ0KfQ0KYGBgDQpOZXh0IHdlIHdpbGwgZG8gdGhlIHNhbWUgdGhpbmcgZm9yIGVhY2ggb2YgdGhlIHN1YmNvbW11bml0aWVzLiBUbyBtYWtlIHRoZSByYXcgZGF0YSBmb3IgdGhlc2Ugd2UgbWFudWFsbHkgZXhwb3J0ZWQgZWFjaCB0YWJsZSBmcm9tIEdlcGhpIGJ5IHRha2luZyB0aGUgZW50aXJlIG5ldHdvcmssIGZpbHRlcmluZyBvdXQgdGhvc2UgYm9va3Mgbm90IGluIHRoZSBmb2NhbCBtb2R1bGFyaXR5IGNsYXNzLCBhbmQgcmVjYWxjdWxhdGluZyB0aGUgR2VwaGkgc3RhdGlzdGljcyB3ZSBjYXJlIGFib3V0IChtb2R1bGFyaXR5IGNsYXNzLCBlaWdlbmNlbnRyYWxpdHksIGFuZCB3ZWlnaHRlZCBkZWdyZWUpLg0KDQpgYGB7cn0NCiMgQ3JlYXRlIHRoZSBzYW1lIHRhYmxlcywgYnV0IG5vdyBmb3Igc3ViY29tbXVuaXRpZXMgb2YgZWFjaCBjb21tdW5pdHkuIA0KZm9yIChuZXR3b3JrLnR5cGUgaW4gbGlzdCgiZW5qb3ltZW50IiwgInJlYWRlciIpKXsNCiAgZm9yIChvcmRlcmluZyBpbiBsaXN0KCJFaWdlbmNlbnRyYWxpdHkiLCAiV2VpZ2h0ZWQgRGVncmVlIikpew0KICAgIA0KICAgIHBhdGggPSBzdHJfZ2x1ZSgiLi9yYXcgZGF0YS97bmV0d29yay50eXBlfSBzdWJjb21tdW5pdGllcy8iKSAgIyBGb2xkZXIgd2l0aCBzdWJjb21tdW5pdHkgZGF0YQ0KICAgIGZpbGVfbmFtZXMgPSBsaXN0LmZpbGVzKHBhdGgsIHBhdHRlcm49IiouY3N2IikgIyBMaXN0IGFsbCAuY3N2IGZpbGVzIGluIHRoaXMgZm9sZGVyDQogICAgDQogICAgIyBDcmVhdGUgdGhlIHRhYmxlIGZvciBlYWNoIGZpbGUNCiAgICBmb3IgKGkgaW4gMTpsZW5ndGgoZmlsZV9uYW1lcykpew0KICAgICAgZiA9IGZpbGVfbmFtZXNbW2ldXQ0KICAgICAgZiA9IHBhc3RlKHBhdGgsIGYsIHNlcCA9ICIiKQ0KICAgICAgcHJpbnQoZikNCiAgICAgIA0KICAgICAgZGYgPSByZWFkLmRlbGltMihmLCBzZXAgPSAiLCIpDQogICAgICBkZi5mdWxsID0gY29tcGFyaXNvbi50YWJsZShkZiwgb3JkZXJpbmcsIHN1YmNvbXVucyA9IFRSVUUpDQogICAgICANCiAgICAgIHdyaXRlLnhsc3goZGYuZnVsbCwgc3RyX2dsdWUoIi4vcHJvY2Vzc2VkIGRhdGEve25ldHdvcmsudHlwZX0gc3ViY29tbXVuaXRpZXMvY29tcF90YWJsZV97bmV0d29yay50eXBlfV97aS0xfV9ieV97b3JkZXJpbmd9Lnhsc3giKSkNCiAgDQogICAgfQ0KICB9DQp9DQpgYGANClRoZXJlJ3MgYSBmZXcgbGFzdCB0YWJsZXMgdG8gY3JlYXRlLiBUaGUgcmVhZGVyIG5ldHdvcmsgYnJlYWtzIGludG8gZml2ZSBjb21tdW5pdGllcyBhbmQgdGhlIGVuam95bWVudCBuZXR3b3JrIGJyZWFrcyBpbnRvIHNldmVuLiBJdCBpcyBtb3JlIHRoYW4gcG9zc2libGUgdGhhdCBpZiB3ZSBmb3JjZSB0aGUgcmVhZGVyIG5ldHdvcmsgdG8gYnJlYWsgaW50byBzZXZlbiBjb21tdW5pdGllcyB0aGVuIHRoZXkgd2lsbCBiZSB0aGUgc2FtZSBjb21tdW5pdGllcyBhcyB0aGUgZW5qb3ltZW50IG5ldHdvcmsuIFZpc2EgdmVyc2EgaWYgd2UgYnJlYWsgdGhlIGVuam95bWVudCBuZXR3b3JrIGludG8gb25seSBmaXZlIGNvbW11bml0aWVzLiANCg0KV2UgY2FuIGNoYW5nZSB0aGUgY29tbXVuaXR5IHNpemUgYnkgZ29pbmcgYmFjayB0byB0aGUgZ2VwaGkgZmlsZXMgZm9yIG91ciBuZXR3b3JrIGFuZCByZWNhbGN1bGF0aW5nIHRoZSBtb2R1bGFyaXR5LCBidXQgbm93IHR3ZWFraW5nIHRoZSByZXNvbHV0aW9uIHVudGlsIHdlIGdldCB0aGUgY29ycmVjdCBudW1iZXIgb2YgY29tbXVuaXRpZXMuDQoNCkZvciB0aGlzIGNoZWNrIHdlIGRvbid0IGdvIHRocm91Z2ggdGhlIHN0ZXBzIG9mIHJlc3RyaWN0aW5nIHRoZSBuZXR3b3JrIHRvIGVhY2ggc3ViY29tbXVuaXR5IGJlZm9yZSBjYWxjdWxhdGluZyBlaWdlbmNlbnRyYWxpdHkgYW5kIHdlaWdodGVkIGRlZ3JlZS4gVGhpcyBpcyBiZWNhdXNlIHRoYXQgc3RlcCBhdCBtb3N0IGNoYW5nZXMgdGhlIG9yZGVyaW5nIHNsaWdodGx5IGFuZCB3ZSBkb24ndCByZXF1aXJlIGl0IGZvciBhIGNoZWNrIHRvIHNlZSBpZiB0aGUgY29tbXVuaXRpZXMgYXJlIHZhZ3VlbHkgdGhlIHNhbWUuDQoNCmBgYHtyfQ0KIyBBZ2Fpbiwgc29ydCBieSBlaWdlbmNlbnRyYWxpdHkgYW5kIHRoZSBvdGhlciBieSB3ZWlnaHRlZCBkZWdyZWUuDQoNCmZvciAob3JkZXJpbmcgaW4gbGlzdCgiRWlnZW5jZW50cmFsaXR5IiwgIldlaWdodGVkIERlZ3JlZSIpKXsNCiAgDQogIHByaW50KHN0cl9nbHVlKCJDcmVhdGluZyBjb21wYXJpc29uIHRhYmxlIGZvciB7bmV0d29yay50eXBlfSwgb3JkZXJlZCBieSB7b3JkZXJpbmd9LiIpKQ0KICANCiAgIyBDcmVhdGUgdGFibGUNCiAgZGYgPSByZWFkLmRlbGltMihzdHJfZ2x1ZSgiLi9yYXcgZGF0YS9mdWxsIGVuam95bWVudCBuZXR3b3JrIDYgY29tbXVuaXRpZXMuY3N2IiksIHNlcCA9ICIsIikgICMgTmFtZSBvZiB0aGUgbmV3IHJlYWRlciBuZXR3b3JrIGRhdGEuDQogIGRmID0gY29tcGFyaXNvbi50YWJsZShkZiwgb3JkZXJpbmcpDQogIA0KICAjIFNhdmUgdGFibGUgaW4gdGhlIHByb2Nlc3NlZCBkYXRhIGZvbGRlcg0KICBwYXRoID0gc3RyX2dsdWUoIi4vcHJvY2Vzc2VkIGRhdGEvY29tcGFyaXNvbl90YWJsZV9lbmpveW1lbnRfNl9jb21uc19ieV97b3JkZXJpbmd9Lnhsc3giKQ0KICB3cml0ZS54bHN4KGRmLCBwYXRoKQ0KICANCiAgcHJpbnQoZGYpDQoNCn0NCmBgYA0KDQoNCg0KDQojIENhbiBTdWJqZWN0cyBwcmVkaWN0IENvbW11bml0eT8NCg0KTm93IHdlIGFzayB3aGV0aGVyIGEgbGlzdCBvZiBzdWJqZWN0cyBmb3IgZWFjaCBib29rIGNhbiBwcmVkaWN0IHRoZSBjb21tdW5pdHkgaXQgaXMgaW4uIA0KDQpgYGB7cn0NCnNkZiA9IHJlYWQuZGVsaW0yKCIuL3JhdyBkYXRhL3N1YmplY3RzX3JlZHVjZWQuY3N2Iiwgc2VwID0gIiwiLCBjb2xDbGFzc2VzID0gIm51bWVyaWMiLCBuYS5zdHJpbmdzID0gIiIpICAjIHJlYWQgaW4gZGF0YWZyYW1lIG9mIHN1YmplY3RzDQpwcmludChzZGYpDQpgYGANCg0KYGBge3J9DQoNCiMgUmVuYW1lIHRoZSBjb2x1bW5zIHRvIGJldHRlciByZWZsZWN0IHBhcGVyJ3MgY29udmVudGlvbnMNCnNkZiA9IHJlbmFtZShzZGYsIGMoWCA9ICJHb29kcmVhZHMgSWQiKSAgKQ0KcHJpbnQoc2RmKQ0KYGBgDQoNCg0KYGBge3J9DQp0YWxseShzZGYpDQpgYGANCg0KDQpgYGB7cn0NCg0KDQojIEZvciB0aGUgcmVhZGVyIGFuZCBlbmpveW1lbnQgbmV0d29yayANCmZvciAobmV0d29yay50eXBlIGluIGxpc3QoImVuam95bWVudCIsICJyZWFkZXIiKSl7DQogICAgDQogIHByaW50KHN0cl9nbHVlKCJDcmVhdGluZyBzdWJqZWN0IHRhYmxlIGZvciB7bmV0d29yay50eXBlfS4iKSkNCiAgDQogICMgUmVhZCBib29rIGRhdGEgdGFibGUgYW5kIHJlbmFtZSBjb2x1bW5zDQogIGRmID0gcmVhZC5kZWxpbTIoc3RyX2dsdWUoIi4vcmF3IGRhdGEvZnVsbCB7bmV0d29yay50eXBlfSBuZXR3b3JrLmNzdiIpLCBzZXAgPSAiLCIpICAjIFJlYWQgdGhlIGNvcnJlY3QgdGFibGUgaGVyZQ0KICBkZiA9IHJlbmFtZShkZiwNCiAgICBjKG1vZHVsYXJpdHlfY2xhc3MgPSAiQ29tbXVuaXR5IiwNCiAgICBlaWdlbmNlbnRyYWxpdHkgPSAiRWlnZW5jZW50cmFsaXR5IiwNCiAgICBXZWlnaHRlZC5EZWdyZWUgPSAiV2VpZ2h0ZWQgRGVncmVlIiwNCiAgICBMYWJlbCA9ICJHb29kcmVhZHMgSWQiLA0KICAgIHRpdGxlID0gIlRpdGxlIikNCiAgICApDQogIA0KICBkZiA9IGlubmVyX2pvaW4oc2RmLCBkZiwgYnk9YygiR29vZHJlYWRzIElkIikpDQogIA0KICAjDQogIA0KICAjIFNhdmUgdGFibGUgaW4gdGhlIHByb2Nlc3NlZCBkYXRhIGZvbGRlcg0KICAjcGF0aCA9IHN0cl9nbHVlKCIuL3Byb2Nlc3NlZCBkYXRhL2NvbXBhcmlzb25fdGFibGVfe25ldHdvcmsudHlwZX1fYnlfe29yZGVyaW5nfS54bHN4IikNCiAgI3dyaXRlLnhsc3goZGYsIHBhdGgpDQogIA0KICAjZGYgPSBsYXBwbHkoZGYsIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHgpKSkNCiAgI2RmID0gZG8uY2FsbChjYmluZCwgZGYpDQogIA0KICAjIGdldCB0aGUgc3VtcyBmb3IgYWxsIHRoZSBzdWJqZWN0IGNvbHVtbnMuIA0KICBub24uc3ViLmNvbHMgPSBjKCJHb29kcmVhZHMgSWQiLCAiVGl0bGUiLCAic3ViamVjdHMiLCAiV2VpZ2h0ZWQgRGVncmVlIiwgIkVpZ2VuY2VudHJhbGl0eSIsICJjbHVzdGVyaW5nIiwgInBhZ2VyYW5rcyIsICJ0cmlhbmdsZXMiLCAiSWQiLCAiRGVncmVlIiwgIkNvbW11bml0eSIsICJiaXBhcnRpdGUiKQ0KICBkZiA9IGNvbFN1bXMoZGZbLCFuYW1lcyhkZikgJWluJSBub24uc3ViLmNvbHNdLCBuYS5ybSA9IFRSVUUgKQ0KICBkZltdDQogIGRmID0gZGZbb3JkZXIoZGZbWyJWMSJdXSwgZGVjcmVhc2luZz1UUlVFKSxdDQoNCiAgDQogIA0KICANCiAgIyBHZXQgdGhlIG51bWJlciBvZiBjb21tdW5pdGllcyBzbyBjYW4gbG9vcCB0aHJvdWdoIHRoZW0NCiAgI2MgPSBjb3VudChkZiwgIkNvbW11bml0eSIpDQogICNudW0uY29tbXVuaXRpZXMgPSBucm93KGMpDQogICNmb3IgKGkgaW4gMDoobnVtLmNvbW11bml0aWVzLTEpKXsNCiAgIyAgY2RmID0gZGZbZGZbIkNvbW11bml0eSJdID09IGksXQ0KICAjICBjZGYgPSBjZGZbb3JkZXIoY2RmW1siRWlnZW5jZW50cmFsaXR5Il1dLCBkZWNyZWFzaW5nPVRSVUUpLF0NCiAgIyAgY2RmID0gc3VtbWFyaXNlX2FsbChjZGYpDQogICMgIHByaW50KGNkZikNCg0KICB9DQogIA0KDQogIA0KICANCiAgDQoNCmBgYA0KDQoNCiMgUENBIEFuYWx5c2lzDQoNCg0KDQoNCldlIGNhbid0IHVzZSB0aGUgcHVyZSBzdWJqZWN0IGNvdW50cyBiZWFjdXNlIHdpdGhvdXQgbm9ybWFsaXppbmcgdGhlIGRhdGEgaW4gc29tZSB3YXkgd2UgaGF2ZSB0aGF0IHBvcHVsYXIgc3ViamVjdHMgYXJlIHRoZSBtb3N0IGluZmx1ZW5jaWFsIGFuZCB0aHVzIGRvbWluYXRlIHRoZSBlbnRpcmUgYW5hbHlzaXMuIA0KDQpSZW1lYmVyIHRoYW4gZm9yIHRoZSANCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWR4bCkNCg0KI2RmID0gcmVhZC5kZWxpbTIoc3RyX2dsdWUoIi4vcmF3IGRhdGEvcmVsYXRpdmVfc3ViamVjdF9jb3VudHNfcmVhZGVyLmNzdiIpLCBzZXAgPSAiLCIpICAjIFJlYWQgdGhlIGNvcnJlY3QgdGFibGUgaGVyZQ0KZGYgPSByZWFkX2V4Y2VsKCIuL3JhdyBkYXRhL3JlbGF0aXZlX3N1YmplY3RfY291bnRzX2Vuam95bWVudC54bHN4IikgICMgUmVhZCB0aGUgY29ycmVjdCB0YWJsZSBoZXJlDQojIEJvdGggdGhlIGFib3ZlIHRhYmxlcyBnaXZlIHRoZSBzYW1lIHBjYSBhbmFseXNpcw0KDQpkZiA9IGRmICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoLiwgdmFyID0gIi4uLjEiKSAgIyBTZXQgbGFiZWwgdG8gcm93IG5hbWVzLiBWYXIgc2hvdWxkIGVxdWFsIHRoZSBuYW1lIG9mIHRoZSBmaXJzdCBjb2x1bW4uDQoNCm51bS5jb21tdW5pdGllcyA9IG5jb2woZGYpIC0gMSAgIyBNaW51cyAxIGFsbCBjb3VudHMgY29sdW1uDQoNCiMgRHJvcCB0aGUgYWxsIGdyb3VwIGJlY2F1c2UgdGhpcyBtYWtlcyBhbGwgcmVzdWx0cyB0aGUgc2FtZQ0KZGYgPSBkZlsgLCAtd2hpY2gobmFtZXMoZGYpICVpbiUgYygiY291bnRfYWxsIikpXSAgDQoNCmZvciAoaSBpbiAwOihudW0uY29tbXVuaXRpZXMtMSkpew0KICBjb2xuYW1lID0gc3RyX2dsdWUoImNvdW50X3tpfSIpDQogIGRmW1tjb2xuYW1lXV0gPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihkZltbY29sbmFtZV1dKSkNCg0KfQ0KDQojIFJlbmFtZSB0aGUgY29sdW1ucyBmb3IgaW50ZXJwcmV0YXRpb24NCiMgTmFtZXMgZm9yIGVuam95bWVudA0KbmFtZXMoZGYpWzE6bnVtLmNvbW11bml0aWVzXSA9IGMoIlRocmlsbGVyIiwgIkZhbnRhc3kvU2NpZmkiLCAiTWFuZ2EiLCAiKE1vZGVybikgQ2xhc3NpY3MiLCAiQ2hpbGRyZW4ncyIsICJDb250ZW1wb3JhcnkvUmVhbGlzdGljIiwgIllvdW5nIEFkdWx0IikNCiMgTmFtZXMgZm9yIHJlYWRlcg0KI25hbWVzKGRmKVsxOm51bS5jb21tdW5pdGllc10gPSBjKCJDaGlsZHJlbidzIiwgIihNb2Rlcm4pIENsYXNzaWNzIiwgIkdlbnJlIEZpY3Rpb24iLCAiWW91bmcgQWR1bHQiLCAiQ29udGVtcG9yYXJ5L1JlYWxpc3RpYyIpDQoNCg0KIyBSZW1vdmUgZmljdGlvbiByb3cNCiNyb3cubmFtZXMucmVtb3ZlIDwtIGMoIkZpY3Rpb24iKQ0KI2RmID0gZGZbIShyb3cubmFtZXMoZGYpICVpbiUgcm93Lm5hbWVzLnJlbW92ZSksIF0NCg0KZGZbImNvdW50c19hbGwiXSA9IE5VTEwNCg0KYGBgDQoNCg0KYGBge3J9DQpzdWJqZWN0cy5wY2EgPSBwcmNvbXAoZGYsY2VudGVyID0gVFJVRSwgc2NhbGUuID0gVFJVRSkNCnN1bW1hcnkoc3ViamVjdHMucGNhKQ0KZnZpel9laWcoc3ViamVjdHMucGNhKQ0KDQpgYGANCldlIHNlZSBhbGwgY29tcG9uZW50cyBhcmUgaW1wb3J0YW50IGV4Y2VwdCB0aGUgZmlmdGguIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSBlYWNoIG9mIHRoZSBlaWdlbnZlY3RvcnMgc2hvdWxkIGNvcnJlc3BvbmQgdG8gYSBjb21tdW5pdHkuIElmIGEgcGNhIGNvdWxkIGV4cGxhaW4gMTAwJSBvZiB0aGUgdmFyaWFuY2UgaW4gdGhlIG51bWJlciBvZiBjb21tdW5pdGllcyB0aGF0IG1lYW5zIHRoYXQgdGhlIGNvbW11bml0aWVzIGhhZCBubyBvdmVybGFwPyBTbyBob3cgZmFyIGF3YXkgZnJvbSB0aGlzIHdlIGFyZSBjb3JyZXNwb25kcyB0byB0aGUgbWl4aW5nIGJldHdlZW4gZ2VucmVzLg0KDQoNCmBgYHtyfQ0KY29vcmRzID0gZ2V0X3BjYV9pbmQoc3ViamVjdHMucGNhKSRjb29yZA0KY29vcmRzLmRmID0gYXMuZGF0YS5mcmFtZShjb29yZHMpDQpgYGANCg0KYGBge3J9DQpnZXRfcGNhKHN1YmplY3RzLnBjYSkkY29yDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpmdml6X3BjYV9iaXBsb3Qoc3ViamVjdHMucGNhLA0KICAgICAgICAgICAgICBjb2wudmFyID0gImNvbnRyaWIiLCAjIENvbG9yIGJ5IGNvbnRyaWJ1dGlvbnMgdG8gdGhlIFBDDQogICAgICAgICAgICAgI2NvbC5pbmQgPSAiY29zMiIsICMgQ29sb3IgYnkgdGhlIHF1YWxpdHkgb2YgcmVwcmVzZW50YXRpb24NCiAgICAgICAgICAgICBncmFkaWVudC5jb2xzID0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSwNCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUsICAgICAjIEF2b2lkIHRleHQgb3ZlcmxhcHBpbmcNCiAgICAgICAgICAgICAjYWRkRWxsaXBzZXMgPSBUUlVFLA0KICAgICAgICAgICAgIGxhYmVsID0gYygicXVhbnRpLnN1cCIsICJ2YXIiKSwNCiAgICAgICAgICAgICBhbHBoYS5pbmQgPSAuMSwNCiAgICAgICAgICAgICBhbHBoYS52YXIgPSAxLA0KICAgICAgICAgICAgIA0KICAgICAgICAgICAgICkNCg0KDQpgYGANCg0KYGBge3J9DQpmdml6X3BjYV92YXIoc3ViamVjdHMucGNhLA0KICAgICAgICAgICAgIGNvbC52YXIgPSAiY29udHJpYiIsICMgQ29sb3IgYnkgY29udHJpYnV0aW9ucyB0byB0aGUgUEMNCiAgICAgICAgICAgICBncmFkaWVudC5jb2xzID0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSwNCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUgICAgICMgQXZvaWQgdGV4dCBvdmVybGFwcGluZw0KICAgICAgICAgICAgICkNCmBgYA0KDQoNCg0KYGBge3J9DQpsaWJyYXJ5KCJjb3JycGxvdCIpDQp2YXIgPC0gZ2V0X3BjYV92YXIoc3ViamVjdHMucGNhKQ0KY29ycnBsb3QodmFyJGNvbnRyaWIsIGlzLmNvcnI9RkFMU0UsIG1ldGhvZD0ic3F1YXJlIikNCg0KY29ycnBsb3QodmFyJGNvciwgaXMuY29ycj1GQUxTRSwgbWV0aG9kPSJzcXVhcmUiLCB0bC5jb2wgPSAxKQ0KYGBgDQoNCg0K